Een Ajax bibliotheek
Home

Een Ajax bibliotheek

Een Ajax bibliotheek

We creëren een object met de naam Ajax waarin we alle functionaliteit van het XMLHttpRequest object onderbrengen.

Bron

Matthew Eernisse, Build Your Own AJAX Web Applications, “Create responsive web applications with the power of AJAX!”. SitePoint Pty. Ltd., Australia, 2006.

Hoe?

In JavaScript declareren we geen klassen met een complexe syntaxis zoals we dat in C# of Java doen. We schrijven een constructor functie om een instantie van een 'klasse' te maken:

De eigenschappen

We beginnen met de constructor voor onze Ajax klasse. We voegen de eigenschappen en methoden toe die we nodig hebben om het XMLHttpRequest object te kunnen werken:

// De Ajax klasseconstructor:
function Ajax() {
       this.request = null;
       this.url = null;
       this.status = null;
       this.statusText = '';
       this.method = 'GET';
       this.asynchronousFlag = true;
       this.postData = null;
       this.readyState = null;
       this.responseText = null;
       this.responseXML = null;
       this.responseHandle = null;
       this.errorHandle = null;
       this.responseFormat = 'text', // 'text', 'xml', 'object'
       this.mimeType = null;
       this.accessToken = null; // access token te gebruiken met oAuth
       this.contentType = 'application/x-www-form-urlencoded'; // default content-type
}

Methoden implementeren

We gaan nu enkele methoden voor de Ajax klasse implementeren. We hebben functies nodig om een het XMLHttpRequest object in te stellen en om requests te versturen.

Een XMLHttpRequest object creëren

De initialize methode maakt een XMLHttpRequest object aan. Hoe je dit object kan aanmaken, verschilt van browser tot browser. Er bestaat een verschil tussen Microsoft Internet Explorer 6 en andere browsers als Opera, Safari, Mozilla, etc. Microsoft Internet Explorer 6 en vroegere versie maken gebruik van ActiveX object, waarbij we er rekening mee moeten houden dat dit uitgeschakeld kan worden. Onze initialize methode moet met elke mogelijkheid rekening houden en op elegante wijze degraderen (dit is lang geleden geschreven, nu is het misschien niet meer nodig om daar rekening mee te houden):

// Methoden:
// Een XMLHttpRequest object maken
this.initialize = function() {
   if (!this.request) {
       try {
           // Probeer een object te creëren voor Firefox, Safari, IE7
           this.request = new XMLHttpRequest();
       }
       catch (e) {
           try {
                // Probeer een object te creëren voor latere versies IE
                this.request = new ActiveXObject('MSXML2.XMLHTTP');
           }
           catch (e) {
                try {
                     // Probeer een object te maken met oudere versies van IE
                     this.request = new ActiveXObject('Microsoft.XMLHTTP');
                }
                catch (e) {
                     // Kan geen XMLHttpRequest maken
                     return false;
                }
           }
       }
   }
   return true;
};

De request openen

De setupRequest methode roept de methode initialize aan, die een XMLHttpRequest object creëert met de naam this.request, en het vervolgens opent zodat de instellingen van de http request bepaald kunnen worden.

// Een request instellen
this.setupRequest = function() {
   // Probeer een XMLHttpRequest object aan te maken, als het niet lukt, zeg het
   // retourneer
   if (!this.initialize()) {
      alert('Het XMLHttpRequest object kan niet gecreëerd worden!');
      return;
   }
   // Een XMLHttpRequest object is met succes gecreëerd
   this.request.open(this.method, this.url, this.asynchronousFlag);
};

De open methode heeft drie parameters:

  1. Method: De meest gebruikte methoden zijn GET en POST. Volgens de HTPP specificatie (RFC 2616) zijn de methoden van die requests hoofdlettergevoelig. Zorg ervoor dat je de methode altijd in hoofdletters meegeeft.
  2. URL: Die parameter geeft aan welke pagina opgevraagd (GET) of verstuurd (POST) wordt.
  3. Asynchronous Flag: Als die parameter is ingesteld op true, gaat je JavaScript verder worden uitgevoerd terwijl je wacht op het antwoord op je request. Is die parameter false, houdt je JavaScript code halt totdat de server het antwoord terug heeft gestuurd. Het is precies de mogelijkheid om asynchroon met de server te communiceren die het nut van het hele Ajax gebeuren uitmaakt. Die parameter moet altijd op true worden ingesteld.

In onze gloednieuwe Ajax klasse worden de method en asynchronousFlag eigenschappen op redelijke standaard waarden ingesteld, namelijk GET en true. Maar de URL eigenschap moet je natuurlijk altijd zelf instellen.

De request header instellen voor geverifiëerde aanvragen

We gaan na of er een accesToken is ingesteld waarmee we een geverifiëerde request kunnen maken:

if (this.accessToken) {
    this.request.setRequestHeader('Authorization', 'Bearer ' + this.accessToken);
}

Het Content-Type Header veld

Het Content-Type veld beschrijft de gegevens die in de body worden meegegeven zodat de ontvangende user agent de gepast methode kan uitkiezen om de gegevens aan de gebruiker te presenteren of om de gegevens te verwerken. De standaard waarde is application/x-www-form-urlencoded. Mogelijke waarden vind je op Content-Type field.

if (this.method == 'POST') {
    this.request.setRequestHeader('Content-Type', this.contentType);
}

De onreadystatechange Event Handler instellen

Nadat we een nieuw XMLHttpRequest object hebben aangemaakt en geopend, moeten we nog aangeven wat er moet gebeuren wanneer er een respons ontvangen wordt. Daarvoor implementeren we de onreadystatechange event handler van het XMLHttpRequest object. Als een HTTP request op de server verwerkt wordt de voortgang aangegeven door wijzigingen in de readyState eigenschap. Die eigenschap is een integer die één van de volgende toestanden weergeeft:

Het XMLHttpRequest object vuurt een event af telkens als de eigenschap readyState verandert. In de afhandelaar voor dit event checken we de readyState eigenschap en als die de waarde 4 heeft (volledig uitgevoerd) handelen we het antwoord van de server af.

Een basisschets voor de eventafhandelaar voor het onreadystatechange event in onze Ajax klasse ziet er zo uit:

// Een request instellen
this.setupRequest = function() {
   // Probeer een XMLHttpRequest object aan te maken, als het niet lukt, zeg het
   // retourneer
   if (!this.initialize()) {
      alert('Het XMLHttpRequest object kan niet gecreëerd worden!');
      return;
   }
   // Een XMLHttpRequest object is met succes gecreëerd
   this.request.open(this.method, this.url, this.asynchronousFlag);
   // Een functie toevoegen om het onreadystatechange event af te handelen
   var me = this; // fix loss of scope in inner function

   this.request.onreadystatechange = function() {
      // handel de event af als de request volledig uitgevoerd is
      if (me.request.readyState == 4) {
      // Verwerk het antwoord van de server
      }
   }
};

Later vullen we de code in. Voor het moment volstaat het om te weten dat je die event handler moet instellen vooraleer de request verzonden wordt.

De request verzenden

Gebruik de send methode van het XMLHttpRequest object om de HTPP request op te starten:

// Een request instellen

this.setupRequest = function() {
   // Probeer een XMLHttpRequest object aan te maken, als het niet lukt, zeg het
   // retourneer
   if (!this.initialize()) {
      alert('Het XMLHttpRequest object kan niet gecreëerd worden!');
      return;
   }
   // Een XMLHttpRequest object is met succes gecreëerd
   this.request.open(this.method, this.url, this.asynchronousFlag);
   // Een functie toevoegen om het onreadystatechange event af te handelen
   var me = this; // fix loss of scope in inner function
   
   this.request.onreadystatechange = function() {
      // handel de event af als de request volledig uitgevoerd is
      if (me.request.readyState == 4) {
         // Verwerk het antwoord van de server
      }
   }
};

// Verstuur de request met de send method van het XMLHttpRequest object
this.request.send(this.postData);

De send methode heeft één parameter die voor POST gegevens gebruikt wordt. Als de request een GET is die geen gegevens doorgeeft wordt die parameter ingesteld op null.

We slaan de waarde van this op in een tijdelijke variabele me omdat de waarde van this bij asynchrone eventafhandelaars uit scope geraken en hun waarde kunnen verliezen.

Het antwoord van de server verwerken

Het is tijd om het antwoord van de server effectief te verwerken. De functie moet drie dingen doen:

  1. Is het antwoord een foutmelding of niet?
  2. Het formaat voor het antwoord voorbereiden.
  3. Het antwoord doorgegeven aan de gewenste functie.

We voegen daarvoor de volgende code toe:

this.request.onreadystatechange = function() {
   // handel de event af als de request volledig uitgevoerd is
   var response = null;
   if (me.request.readyState == 4) {
      // Bereid het antwoord voor in het gewenste formaat
      switch (me.responseFormat) {
      case 'text':
         response = me.request.responseText;
         break;
      case 'xml':
         response = me.request.responseXML;
         break;
      case 'object':
         response = request;
         break;
      }
      // Als er een fout is opgetreden handel die af, in het andere geval
      // geef het antwoord door aan de functie die het antwoord afhandelt
      if (me.request.status >= 200 && me.request.status <= 299) {
         if (!me.responseHandle) {
            alert('Geen response handler gedefiniëerd voor dit ' +
                'XMLHttpRequest object.');
            return;
         }
         else {
            me.responseHandle(response);
         }
      }
      else {
         me.errorHandle(response);
      }
   }
};

Als het antwoord volledig is teruggestuurd door de server wordt de status eigenschap van het XMLHttpRequest object ingesteld. Die eigenschap bevat een HTTP status code van de uitgevoerde request. De code kan 404 zijn als de pagina niet gevonden wordt, 500 als de fout optrad in de script aan de serverzijde, 200 als de request met succes is uitgevoerd enz. Een volledige lijst van die codes vind je in de HTTP specificatie (RFC 2616).

Het is haast onmogelijk om al die foutcodes te onthouden. Gelukkig heeft het XMLHttpRequest object een eigenschap statusText die een korte beschrijving van de fout bevat.

Onze Ajax basisklasse kan het antwoord van de server verwerken in drie formaten, als een gewone JavaScript string, als een XML document object toegankelijk via de W3C XML DOM en als een XMLHttpRequest object dat gebruikt werd om de request op te zetten.

De inhoud van het antwoord kan via twee eigenschappen van het XMLHttpRequest object opgehaald worden:

  1. responseText: die eigenschap bevat het antwoord van de server als een gewone string. Als er een fout is opgetreden bevat die eigenschap HTML foutpagina van de server. Vanaf er antwoord gekomen is van de server (readyState = 4) bevat die eigenschap gegevens.
  2. responseXML: die eigenschap bevat een XML document. Als het antwoord niet in XML formaat is, is die eigenschap leeg.

Standaard wordt de responseFormat eigenschap van onze Ajax klasse ingesteld op text. De response handler retourneert het antwoord van de server als een JavaScript string. Als je met XML werkt moet je eigenschap instellen op xml. De laatste mogelijkheid bestaat erin de eigenschap in te stellen op object waardoor je een XMLHttpRequest object terugkrijgt. Hetzelfde als je verstuurd hebt.

De foutafhandelaar

Als de status eigenschap aangeeft dat er een fout is opgetreden tijdens de request (de waarde ervan ligt buiten het interval 200 en 299) wordt het antwoord van de server doorgegeven aan de error handler in de errorHandler eigenschap. De errorHandler verwijst naar de volgende functie:

this.errorHandle = function() {
   var errorWindow;
   // Toon een foutmelding in een popup window
   try {
      errorWindow = window.open('', 'errorWin');
      errorWindow.document.body.innerHTML = this.responseText;
   }
   // Als pop-ups in de browser geblokkeerd zijn, zeg het aan de gebruiker
   catch(e) {
      alert('Er is een fout opgetreden, maar de foutmelding kan niet ' +
          ' getoond worden omdat je browser pop-ups blokkeert.\n' +
          'Je moet pop-ups toelaten voor die Web site als je de foutmeldingen wil zien.');
   }
}:

De request afbreken

Het gebeurt dat het lang duurt vooraleer een pagina in de browser geladen wordt. De browser heeft een stop knop. Onze Ajax klasse moet daar ook rekening mee houden en kunnen afbreken. Daarvoor voegen we de volgende methode aan onze klasse toe:

// De request annuleren
this.abort = function() {
   if (this.request) {
      this.request.onreadystatechange = function() { };
      this.request.abort();
      this.request = null;
   }
};

Die methode stelt de onreadystatechange event handler in op een lege functie, roept de abort methode van het XMLHttpRequest object aan en vernietigt tenslotte het object. Je moet de onreadystatechange event handler op nul instellen om vele implementaties van het XMLHttpRequest object die onreadystate event afvuren als de request wordt afgebroken.

Een GET request versturen

De Ajax klasse heeft nog vier dingen nodig om te kunnen werken:

  1. een URL;
  2. een handler functie voor het antwoord;
  3. de methode accepteert een derde optionele parameter waarmee je het standaard ingestelde formaat kan wijzigen;
  4. met de vierde parameter kan je een access token meegeven om een geverifiëerde request te maken;

We voegen de getRequest methode toe:

this.getRequest = function(url, handle, format, accessToken) {
   this.url = url;
   this.responseHandle = handle;
   this.responseFormat = format || 'text';
   this.accessToken = accessToken;
   this.setupRequest();
};

Een POST request versturen

Met de methode postRequest kan je gegevens in de body van de request naar de server versturen. Daarom heeft deze methode twee extra parameters:

  1. format: het formaat waarin de gegevens door de server moeten worden teruggestuurd;
  2. contentType: het formaat waarin de gegevens naar de server worden gestuurd; voor Google API call's is dat bijvoorbeeld application/json; zo weet de server van welk soort gegevenstype de gegevens zijn die binnenkomen;
this.postRequest = function(url, postData, handle, format, contentType, accessToken) {
    this.url = url;
    this.responseHandle = handle;
    this.responseFormat = format || 'text';
    this.contentType = contentType || 'application/x-www-form-urlencoded'
    this.accessToken = accessToken;
    this.method = 'POST';
    this.postData = postData;
    this.setupRequest();
};

Een DELETE request versturen

Een DELETE request is zeer eenvoudig. Als data heeft je alleen een id van het te deleted item mee.

this.deleteRequest = function(url, handle, accessToken) {
    this.url = url;
    this.responseHandle = handle;
    this.accessToken = accessToken;
    this.method = 'DELETE';
    this.setupRequest();
};

Het ajax.js bestand

De volledige code stop je in een bestand met de naam ajax.js:

/*
 * Original idee by Matthew Eernisse (mde@fleegix.org)
 * 2007 Joseph Inghelbrecht CVO Deurne, www.cvodeurne.be
 * 12 juli 2007, zomer in Rièzes, de zon schijnt en de kat slaapt.
 * 1 november 2016, herfst in Les Charmontois.
 */

// De Ajax klasseconstructor:
function Ajax() {
    this.request = null;
    this.url = null;
    this.status = null;
    this.statusText = '';
    this.method = 'GET';
    this.asynchronousFlag = true;
    this.postData = null;
    this.readyState = null;
    this.responseText = null;
    this.responseXML = null;
    this.responseHandle = null;
    this.errorHandle = null;
    this.responseFormat = 'text'; // 'text', 'xml', 'object'
    this.mimeType = null;
    this.accessToken = null; // te gebruiken met oAuth
    this.contentType = 'application/x-www-form-urlencoded'; // default content-type

    // Methoden:
    // Een XMLHttpRequest object maken
    this.initialize = function() {
        if (!this.request) {
            try {
                // Probeer een object te creëren voor Firefox, Safari, IE7
                this.request = new XMLHttpRequest();
            }
            catch (e) {
                try {
                    // Probeer een object te creëren voor latere versies IE
                    this.request = new ActiveXObject('MSXML2.XMLHTTP');
                }
                catch (e) {
                    try {
                        // Probeer een object te maken met oudere versies van IE
                        this.request = new ActiveXObject('Microsoft.XMLHTTP');
                    }
                    catch (e) {
                        // Kan geen XMLHttpRequest maken
                        return false;
                    }
                }
            }
        }
        return true;
    };

    // Een request instellen
    this.setupRequest = function() {
        // Probeer een XMLHttpRequest object aan te maken, als het niet lukt, zeg het
        // retourneer
        if (!this.initialize()) {
            alert('Het XMLHttpRequest object kan niet gecreëerd worden!');
            return;
        }
        // Een XMLHttpRequest object is met succes gecreëerd
        this.request.open(this.method, this.url, this.asynchronousFlag);
        // Making authenticated request using the request header is accessToken is set
        if (this.accessToken) {
            this.request.setRequestHeader('Authorization', 'Bearer ' + this.accessToken);
        }

        if (this.method == 'POST') {
            this.request.setRequestHeader('Content-Type', this.contentType);
        }
        var me = this; // fix loss of scope in inner function
        // Een functie toevoegen om het onreadystatechange event af te handelen
        this.request.onreadystatechange = function() {
            // handel de event af als de request volledig uitgevoerd is
            var response = null;
            if (me.request.readyState == 4) {
                // Bereid het antwoord voor in het gewenste formaat
                switch (me.responseFormat) {
                    case 'text':
                        response = me.request.responseText;
                        break;
                    case 'xml':
                        response = me.request.responseXML;
                        break;
                    case 'object':
                        response = request;
                        break;
                }
                // Als er een fout is opgetreden handel die af, in het andere geval
                // geef het antwoord door aan de functie die het antwoord afhandelt
                if (me.request.status >= 200 && me.request.status <= 299) {
                    if (!me.responseHandle) {
                        alert('Geen response handler gedefiniëerd voor dit ' +
                            'XMLHttpRequest object.');
                        return;
                    }
                    else {
                        me.responseHandle(response);
                    }
                }
                else {
                    me.errorHandle(response);
                }
            }
        };
        // Verstuur de request met de send method van het XMLHttpRequest object
        this.request.send(this.postData);
    };

    this.getRequest = function(url, handle, format, accessToken) {
        this.url = url;
        this.responseHandle = handle;
        this.responseFormat = format || 'text';
        this.accessToken = accessToken;
        this.setupRequest();
    };

    this.postRequest = function(url, postData, handle, format, contentType, accessToken) {
        this.url = url;
        this.responseHandle = handle;
        this.responseFormat = format || 'text';
        this.contentType = contentType || 'application/x-www-form-urlencoded'
        this.accessToken = accessToken;
        this.method = 'POST';
        this.postData = postData;
        this.setupRequest();
    };

    this.deleteRequest = function(url, handle, accessToken) {
        this.url = url;
        this.responseHandle = handle;
        this.accessToken = accessToken;
        this.method = 'DELETE';
        this.setupRequest();
    };

    this.errorHandle = function() {
        var errorWindow;
        // Toon een foutmelding in een popup window
        try {
            errorWindow = window.open('', 'errorWin');
            errorWindow.document.body.innerHTML = this.responseText;
        }
        // Als pop-ups in de browser geblokkeerd zijn, zeg het aan de gebruiker
        catch (e) {
            // alert('Er is een fout opgetreden, maar de foutmelding kan niet ' +
            // ' getoond worden omdat je browser pop-ups blokkeert.\n' +
            // 'Je moet pop-ups toelaten voor die Web site als je de foutmeldingen wil zien.');
            var feedback = document.getElementById('feedback');
            if (!feedback) {
                feedback = document.createElement('div');
                document.body.appendChild(feedback);
            }
            var p = document.createElement('p');
            var textContent = document.createTextNode(this.responseText);
            p.appendChild(textContent);
            feedback.appendChild(p);
        }
    };

    // De request annuleren
    this.abort = function() {
        if (this.request) {
            this.request.onreadystatechange = function() {};
            this.request.abort();
            this.request = null;
        }
    };
}
JI
2017-04-24 21:13:58